<?xml version="1.0" encoding="utf-8" standalone="no"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
  "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="zh-CN">
<head>
  <title></title>
  <link href="../Styles/stylesheet.css" rel="stylesheet" type="text/css" />
  
<style type="text/css">
@page { margin-bottom: 5.000000pt; margin-top: 5.000000pt; }
</style>
</head>

<body>
  <h2><span style="border-bottom:1px solid">Chapter_11</span></h2>

  <p>Groundhog2 gh = new Groundhog2(3);</p>

  <p>if(ht.containsKey(gh))</p>

  <p>System.out.println((Prediction)ht.get(gh));</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>注意这段代码使用了来自前一个例子的Prediction，所以SpringDetector.java必须首先编译，否则就会在试图编译SpringDetector2.java时得到一个编译期错误。</p>

  <p>Groundhog2.hashCode()将土拔鼠号码作为一个标识符返回（在这个例子中，程序员需要保证没有两个土拔鼠用同样的ID号码并存）。为了返回一个独一无二的标识符，并不需要hashCode()，equals()方法必须能够严格判断两个对象是否相等。</p>

  <p>equals()方法要进行两种检查：检查对象是否为null；若不为null，则继续检查是否为Groundhog2的一个实例（要用到instanceof关键字，第11章会详加论述）。即使为了继续执行equals()，它也应该是一个Groundhog2。正如大家看到的那样，这种比较建立在实际ghNumber的基础上。这一次一旦我们运行程序，就会看到它终于产生了正确的输出（许多Java库的类都覆盖了hashcode()和equals()方法，以便与自己提供的内容适应）。</p>

  <p>2. 属性：Hashtable的一种类型</p>

  <p>在本书的第一个例子中，我们使用了一个名为Properties（属性）的Hashtable类型。在那个例子中，下述程序行：</p>

  <p>Properties p = System.getProperties();</p>

  <p>p.list(System.out);</p>

  <p>调用了一个名为getProperties()的static方法，用于获得一个特殊的Properties对象，对系统的某些特征进行描述。list()属于Properties的一个方法，可将内容发给我们选择的任何流式输出。也有一个save()方法，可用它将属性列表写入一个文件，以便日后用load()方法读取。</p>

  <p>尽管Properties类是从Hashtable继承的，但它也包含了一个散列表，用于容纳“默认”属性的列表。所以假如没有在主列表里找到一个属性，就会自动搜索默认属性。</p>

  <p>Properties类亦可在我们的程序中使用（第17章的ClassScanner.java便是一例）。在Java库的用户文档中，往往可以找到更多、更详细的说明。</p>

  <p>8.4.5 再论枚举器</p>

  <p>我们现在可以开始演示Enumeration（枚举）的真正威力：将穿越一个序列的操作与那个序列的基础结构分隔开。在下面的例子里，PrintData类用一个Enumeration在一个序列中移动，并为每个对象都调用toString()方法。此时创建了两个不同类型的集合：一个Vector和一个Hashtable。并且在它们里面分别填充Mouse和Hamster对象（本章早些时候已定义了这些类；注意必须先编译HamsterMaze.java和WorksAnyway.java，否则下面的程序不能编译）。由于Enumeration隐藏了基层集合的结构，所以PrintData不知道或者不关心Enumeration来自于什么类型的集合：</p>

  <p>//: Enumerators2.java</p>

  <p>// Revisiting Enumerations</p>

  <p>import java.util.*;</p>

  <p>class PrintData {</p>

  <p>static void print(Enumeration e) {</p>

  <p>while(e.hasMoreElements())</p>

  <p>System.out.println(</p>

  <p>e.nextElement().toString());</p>

  <p>}</p>

  <p>}</p>

  <p>class Enumerators2 {</p>

  <p>public static void main(String[] args) {</p>

  <p>Vector v = new Vector();</p>

  <p>for(int i = 0; i &lt; 5; i++)</p>

  <p>v.addElement(new Mouse(i));</p>

  <p>Hashtable h = new Hashtable();</p>

  <p>for(int i = 0; i &lt; 5; i++)</p>

  <p>h.put(new Integer(i), new Hamster(i));</p>

  <p>System.out.println("Vector");</p>

  <p>PrintData.print(v.elements());</p>

  <p>System.out.println("Hashtable");</p>

  <p>PrintData.print(h.elements());</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>注意PrintData.print()利用了这些集合中的对象属于Object类这一事实，所以它调用了toString()。但在解决自己的实际问题时，经常都要保证自己的Enumeration穿越某种特定类型的集合。例如，可能要求集合中的所有元素都是一个Shape（几何形状），并含有draw()方法。若出现这种情况，必须从Enumeration.nextElement()返回的Object进行下溯造型，以便产生一个Shape。</p>

  <p>8.5 排序</p>

  <p>Java 1.0和1.1库都缺少的一样东西是算术运算，甚至没有最简单的排序运算方法。因此，我们最好创建一个Vector，利用经典的Quicksort（快速排序）方法对其自身进行排序。</p>

  <p>编写通用的排序代码时，面临的一个问题是必须根据对象的实际类型来执行比较运算，从而实现正确的排序。当然，一个办法是为每种不同的类型都写一个不同的排序方法。然而，应认识到假若这样做，以后增加新类型时便不易实现代码的重复利用。</p>

  <p>程序设计一个主要的目标就是“将发生变化的东西同保持不变的东西分隔开”。在这里，保持不变的代码是通用的排序算法，而每次使用时都要变化的是对象的实际比较方法。因此，我们不可将比较代码“硬编码”到多个不同的排序例程内，而是采用“回调”技术。利用回调，经常发生变化的那部分代码会封装到它自己的类内，而总是保持相同的代码则“回调”发生变化的代码。这样一来，不同的对象就可以表达不同的比较方式，同时向它们传递相同的排序代码。</p>

  <p>下面这个“接口”（Interface）展示了如何比较两个对象，它将那些“要发生变化的东西”封装在内：</p>

  <p>//: Compare.java</p>

  <p>// Interface for sorting callback:</p>

  <p>package c08;</p>

  <p>interface Compare {</p>

  <p>boolean lessThan(Object lhs, Object rhs);</p>

  <p>boolean lessThanOrEqual(Object lhs, Object rhs);</p>

  <p>} ///:~</p>

  <p>对这两种方法来说，lhs代表本次比较中的“左手”对象，而rhs代表“右手”对象。</p>

  <p>可创建Vector的一个子类，通过Compare实现“快速排序”。对于这种算法，包括它的速度以及原理等等，在此不具体说明。欲知详情，可参考Binstock和Rex编著的《Practical Algorithms for Programmers》，由Addison-Wesley于1995年出版。</p>

  <p>//: SortVector.java</p>

  <p>// A generic sorting vector</p>

  <p>package c08;</p>

  <p>import java.util.*;</p>

  <p>public class SortVector extends Vector {</p>

  <p>private Compare compare; // To hold the callback</p>

  <p>public SortVector(Compare comp) {</p>

  <p>compare = comp;</p>

  <p>}</p>

  <p>public void sort() {</p>

  <p>quickSort(0, size() - 1);</p>

  <p>}</p>

  <p>private void quickSort(int left, int right) {</p>

  <p>if(right &gt; left) {</p>

  <p>Object o1 = elementAt(right);</p>

  <p>int i = left - 1;</p>

  <p>int j = right;</p>

  <p>while(true) {</p>

  <p>while(compare.lessThan(</p>

  <p>elementAt(++i), o1))</p>

  <p>;</p>

  <p>while(j &gt; 0)</p>

  <p>if(compare.lessThanOrEqual(</p>

  <p>elementAt(--j), o1))</p>

  <p>break; // out of while</p>

  <p>if(i &gt;= j) break;</p>

  <p>swap(i, j);</p>

  <p>}</p>

  <p>swap(i , right);</p>

  <p>quickSort(left, i-1);</p>

  <p>quickSort(i+1, right);</p>

  <p>}</p>

  <p>}</p>

  <p>private void swap(int loc1, int loc2) {</p>

  <p>Object tmp = elementAt(loc1);</p>

  <p>setElementAt(elementAt(loc2), loc1);</p>

  <p>setElementAt(tmp, loc2);</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>现在，大家可以明白“回调”一词的来历，这是由于quickSort()方法“往回调用”了Compare中的方法。从中亦可理解这种技术如何生成通用的、可重复利用（再生）的代码。</p>

  <p>为使用SortVector，必须创建一个类，令其为我们准备排序的对象实现Compare。此时内部类并不显得特别重要，但对于代码的组织却是有益的。下面是针对String对象的一个例子：</p>

  <p>//: StringSortTest.java</p>

  <p>// Testing the generic sorting Vector</p>

  <p>package c08;</p>

  <p>import java.util.*;</p>

  <p>public class StringSortTest {</p>

  <p>static class StringCompare implements Compare {</p>

  <p>public boolean lessThan(Object l, Object r) {</p>

  <p>return ((String)l).toLowerCase().compareTo(</p>

  <p>((String)r).toLowerCase()) &lt; 0;</p>

  <p>}</p>

  <p>public boolean</p>

  <p>lessThanOrEqual(Object l, Object r) {</p>

  <p>return ((String)l).toLowerCase().compareTo(</p>

  <p>((String)r).toLowerCase()) &lt;= 0;</p>

  <p>}</p>

  <p>}</p>

  <p>public static void main(String[] args) {</p>

  <p>SortVector sv =</p>

  <p>new SortVector(new StringCompare());</p>

  <p>sv.addElement("d");</p>

  <p>sv.addElement("A");</p>

  <p>sv.addElement("C");</p>

  <p>sv.addElement("c");</p>

  <p>sv.addElement("b");</p>

  <p>sv.addElement("B");</p>

  <p>sv.addElement("D");</p>

  <p>sv.addElement("a");</p>

  <p>sv.sort();</p>

  <p>Enumeration e = sv.elements();</p>

  <p>while(e.hasMoreElements())</p>

  <p>System.out.println(e.nextElement());</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>内部类是“静态”（Static）的，因为它毋需连接一个外部类即可工作。</p>

  <p>大家可以看到，一旦设置好框架，就可以非常方便地重复使用象这样的一个设计――只需简单地写一个类，将“需要发生变化”的东西封装进去，然后将一个对象传给SortVector即可。</p>

  <p>比较时将字串强制为小写形式，所以大写A会排列于小写a的旁边，而不会移动一个完全不同的地方。然而，该例也显示了这种方法的一个不足，因为上述测试代码按照出现顺序排列同一个字母的大写和小写形式：A a b B c C d D。但这通常不是一个大问题，因为经常处理的都是更长的字串，所以上述效果不会显露出来（Java 1.2的集合提供了排序功能，已解决了这个问题）。</p>

  <p>继承（extends）在这儿用于创建一种新类型的Vector――也就是说，SortVector属于一种Vector，并带有一些附加的功能。继承在这里可发挥很大的作用，但了带来了问题。它使一些方法具有了final属性（已在第7章讲述），所以不能覆盖它们。如果想创建一个排好序的Vector，令其只接收和生成String对象，就会遇到麻烦。因为addElement()和elementAt()都具有final属性，而且它们都是我们必须覆盖的方法，否则便无法实现只能接收和产生String对象。</p>

  <p>但在另一方面，请考虑采用“合成”方法：将一个对象置入一个新类的内部。此时，不是改写上述代码来达到这个目的，而是在新类里简单地使用一个SortVector。在这种情况下，用于实现Compare接口的内部类就可以“匿名”地创建。如下所示：</p>

  <p>//: StrSortVector.java</p>

  <p>// Automatically sorted Vector that</p>

  <p>// accepts and produces only Strings</p>

  <p>package c08;</p>

  <p>import java.util.*;</p>

  <p>public class StrSortVector {</p>

  <p>private SortVector v = new SortVector(</p>

  <p>// Anonymous inner class:</p>

  <p>new Compare() {</p>

  <p>public boolean</p>

  <p>lessThan(Object l, Object r) {</p>

  <p>return</p>

  <p>((String)l).toLowerCase().compareTo(</p>

  <p>((String)r).toLowerCase()) &lt; 0;</p>

  <p>}</p>

  <p>public boolean</p>

  <p>lessThanOrEqual(Object l, Object r) {</p>

  <p>return</p>

  <p>((String)l).toLowerCase().compareTo(</p>

  <p>((String)r).toLowerCase()) &lt;= 0;</p>

  <p>}</p>

  <p>}</p>

  <p>);</p>

  <p>private boolean sorted = false;</p>

  <p>public void addElement(String s) {</p>

  <p>v.addElement(s);</p>

  <p>sorted = false;</p>

  <p>}</p>

  <p>public String elementAt(int index) {</p>

  <p>if(!sorted) {</p>

  <p>v.sort();</p>

  <p>sorted = true;</p>

  <p>}</p>

  <p>return (String)v.elementAt(index);</p>

  <p>}</p>

  <p>public Enumeration elements() {</p>

  <p>if(!sorted) {</p>

  <p>v.sort();</p>

  <p>sorted = true;</p>

  <p>}</p>

  <p>return v.elements();</p>

  <p>}</p>

  <p>// Test it:</p>

  <p>public static void main(String[] args) {</p>

  <p>StrSortVector sv = new StrSortVector();</p>

  <p>sv.addElement("d");</p>

  <p>sv.addElement("A");</p>

  <p>sv.addElement("C");</p>

  <p>sv.addElement("c");</p>

  <p>sv.addElement("b");</p>

  <p>sv.addElement("B");</p>

  <p>sv.addElement("D");</p>

  <p>sv.addElement("a");</p>

  <p>Enumeration e = sv.elements();</p>

  <p>while(e.hasMoreElements())</p>

  <p>System.out.println(e.nextElement());</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>这样便可快速再生来自SortVector的代码，从而获得希望的功能。然而，并不是来自SortVector和Vector的所有public方法都能在StrSortVector中出现。若按这种形式再生代码，可在新类里为包含类内的每一个方法都生成一个定义。当然，也可以在刚开始时只添加少数几个，以后根据需要再添加更多的。新类的设计最终会稳定下来。</p>

  <p>这种方法的好处在于它仍然只接纳String对象，也只产生String对象。而且相应的检查是在编译期间进行的，而非在运行期。当然，只有addElement()和elementAt()才具备这一特性；elements()仍然会产生一个Enumeration（枚举），它在编译期的类型是未定的。当然，对Enumeration以及在StrSortVector中的类型检查会照旧进行；如果真的有什么错误，运行期间会简单地产生一个违例。事实上，我们在编译或运行期间能保证一切都正确无误吗？（也就是说，“代码测试时也许不能保证”，以及“该程序的用户有可能做一些未经我们测试的事情”）。尽管存在其他选择和争论，使用继承都要容易得多，只是在造型时让人深感不便。同样地，一旦为Java加入参数化类型，就有望解决这个问题。</p>

  <p>大家在这个类中可以看到有一个名为“sorted”的标志。每次调用addElement()时，都可对Vector进行排序，而且将其连续保持在一个排好序的状态。但在开始读取之前，人们总是向一个Vector添加大量元素。所以与其在每个addElement()后排序，不如一直等到有人想读取Vector，再对其进行排序。后者的效率要高得多。这种除非绝对必要，否则就不采取行动的方法叫作“懒惰求值”（还有一种类似的技术叫作“懒惰初始化”――除非真的需要一个字段值，否则不进行初始化）。</p>

  <p>8.6 通用集合库</p>

  <p>通过本章的学习，大家已知道标准Java库提供了一些特别有用的集合，但距完整意义的集合尚远。除此之外，象排序这样的算法根本没有提供支持。C++出色的一个地方就是它的库，特别是“标准模板库”（STL）提供了一套相当完整的集合，以及许多象排序和检索这样的算法，可以非常方便地对那些集合进行操作。有感这一现状，并以这个模型为基础，ObjectSpace公司设计了Java版本的“通用集合库”（从前叫作“Java通用库”，即JGL；但JGL这个缩写形式侵犯了Sun公司的版权――尽管本书仍然沿用这个简称）。这个库尽可能遵照STL的设计（照顾到两种语言间的差异）。JGL实现了许多功能，可满足对一个集合库的大多数常规需求，它与C++的模板机制非常相似。JGL包括相互链接起来的列表、设置、队列、映射、堆栈、序列以及反复器，它们的功能比Enumeration（枚举）强多了。同时提供了一套完整的算法，如检索和排序等。在某些方面，ObjectSpace的设计也显得比Sun的库设计方案“智能”一些。举个例子来说，JGL集合中的方法不会进入final状态，所以很容易继承和改写那些方法。</p>

  <p>JGL已包括到一些厂商发行的Java套件中，而且ObjectSpace公司自己也允许所有用户免费使用JGL，包括商业性的使用。详细情况和软件下载可访问http://www.ObjectSpace.com。与JGL配套提供的联机文档做得非常好，可作为自己的一个绝佳起点使用。</p>

  <p>8.7 新集合</p>

  <p>对我来说，集合类属于最强大的一种工具，特别适合在原创编程中使用。大家可能已感觉到我对Java 1.1提供的集合多少有点儿失望。因此，看到Java 1.2对集合重新引起了正确的注意后，确实令人非常愉快。这个版本的集合也得到了完全的重新设计（由Sun公司的Joshua Bloch）。我认为新设计的集合是Java 1.2中两项最主要的特性之一（另一项是Swing库，将在第13章叙述），因为它们极大方便了我们的编程，也使Java变成一种更成熟的编程系统。</p>

  <p>有些设计使得元素间的结合变得更紧密，也更容易让人理解。例如，许多名字都变得更短、更明确了，而且更易使用；类型同样如此。有些名字进行了修改，更接近于通俗：我感觉特别好的一个是用“反复器”（Inerator）代替了“枚举”（Enumeration）。</p>

  <p>此次重新设计也加强了集合库的功能。现在新增的行为包括链接列表、队列以及撤消组队（即“双终点队列”）。</p>

  <p>集合库的设计是相当困难的（会遇到大量库设计问题）。在C++中，STL用多个不同的类来覆盖基础。这种做法比起STL以前是个很大的进步，那时根本没做这方面的考虑。但仍然没有很好地转换到Java里面。结果就是一大堆特别容易混淆的类。在另一个极端，我曾发现一个集合库由单个类构成：colleciton，它同时作为Vector和Hashtable使用。新集合库的设计者则希望达到一种新的平衡：实现人们希望从一个成熟集合库上获得的完整功能，同时又要比STL和其他类似的集合库更易学习和使用。这样得到的结果在某些场合显得有些古怪。但和早期Java库的一些决策不同，这些古怪之处并非偶然出现的，而是以复杂性作为代价，在进行仔细权衡之后得到的结果。这样做也许会延长人们掌握一些库概念的时间，但很快就会发现自己很乐于使用那些新工具，而且变得越来越离不了它。</p>

  <p>新的集合库考虑到了“容纳自己对象”的问题，并将其分割成两个明确的概念：</p>

  <p>(1) 集合（Collection）：一组单独的元素，通常应用了某种规则。在这里，一个List（列表）必须按特定的顺序容纳元素，而一个Set（集）不可包含任何重复的元素。相反，“包”（Bag）的概念未在新的集合库中实现，因为“列表”已提供了类似的功能。</p>

  <p>(2) 映射（Map）：一系列“键－值”对（这已在散列表身上得到了充分的体现）。从表面看，这似乎应该成为一个“键－值”对的“集合”，但假若试图按那种方式实现它，就会发现实现过程相当笨拙。这进一步证明了应该分离成单独的概念。另一方面，可以方便地查看Map的某个部分。只需创建一个集合，然后用它表示那一部分即可。这样一来，Map就可以返回自己键的一个Set、一个包含自己值的List或者包含自己“键－值”对的一个List。和数组相似，Map可方便扩充到多个“维”，毋需涉及任何新概念。只需简单地在一个Map里包含其他Map（后者又可以包含更多的Map，以此类推）。</p>

  <p>Collection和Map可通过多种形式实现，具体由编程要求决定。下面列出的是一个帮助大家理解的新集合示意图：</p>

  <p>这张图刚开始的时候可能让人有点儿摸不着头脑，但在通读了本章以后，相信大家会真正理解它实际只有三个集合组件：Map，List和Set。而且每个组件实际只有两、三种实现方式（注释⑥），而且通常都只有一种特别好的方式。只要看出了这一点，集合就不会再令人生畏。</p>

  <p>⑥：写作本章时，Java 1.2尚处于β测试阶段，所以这张示意图没有包括以后会加入的TreeSet。</p>

  <p>虚线框代表“接口”，点线框代表“抽象”类，而实线框代表普通（实际）类。点线箭头表示一个特定的类准备实现一个接口（在抽象类的情况下，则是“部分”实现一个接口）。双线箭头表示一个类可生成箭头指向的那个类的对象。例如，任何集合都可以生成一个反复器（Iterator），而一个列表可以生成一个ListIterator（以及原始的反复器，因为列表是从集合继承的）。</p>

  <p>致力于容纳对象的接口是Collection，List，Set和Map。在传统情况下，我们需要写大量代码才能同这些接口打交道。而且为了指定自己想使用的准确类型，必须在创建之初进行设置。所以可能创建下面这样的一个List：</p>

  <p>List x = new LinkedList();</p>

  <p>当然，也可以决定将x作为一个LinkedList使用（而不是一个普通的List），并用x负载准确的类型信息。使用接口的好处就是一旦决定改变自己的实施细节，要做的全部事情就是在创建的时候改变它，就象下面这样：</p>

  <p>List x = new ArrayList();</p>

  <p>其余代码可以保持原封不动。</p>

  <p>在类的分级结构中，可看到大量以“Abstract”（抽象）开头的类，这刚开始可能会使人感觉迷惑。它们实际上是一些工具，用于“部分”实现一个特定的接口。举个例子来说，假如想生成自己的Set，就不是从Set接口开始，然后自行实现所有方法。相反，我们可以从AbstractSet继承，只需极少的工作即可得到自己的新类。尽管如此，新集合库仍然包含了足够的功能，可满足我们的几乎所有需求。所以考虑到我们的目的，可忽略所有以“Abstract”开头的类。</p>

  <p>因此，在观看这张示意图时，真正需要关心的只有位于最顶部的“接口”以及普通（实际）类――均用实线方框包围。通常需要生成实际类的一个对象，将其上溯造型为对应的接口。以后即可在代码的任何地方使用那个接口。下面是一个简单的例子，它用String对象填充一个集合，然后打印出集合内的每一个元素：</p>

  <p>//: SimpleCollection.java</p>

  <p>// A simple example using the new Collections</p>

  <p>package c08.newcollections;</p>

  <p>import java.util.*;</p>

  <p>public class SimpleCollection {</p>

  <p>public static void main(String[] args) {</p>

  <p>Collection c = new ArrayList();</p>

  <p>for(int i = 0; i &lt; 10; i++)</p>

  <p>c.add(Integer.toString(i));</p>

  <p>Iterator it = c.iterator();</p>

  <p>while(it.hasNext())</p>

  <p>System.out.println(it.next());</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>新集合库的所有代码示例都置于子目录newcollections下，这样便可提醒自己这些工作只对于Java 1.2有效。这样一来，我们必须用下述代码来调用程序：</p>

  <p>java c08.newcollections.SimpleCollection</p>

  <p>采用的语法与其他程序是差不多的。</p>

  <p>大家可以看到新集合属于java.util库的一部分，所以在使用时不需要再添加任何额外的import语句。</p>

  <p>main()的第一行创建了一个ArrayList对象，然后将其上溯造型成为一个集合。由于这个例子只使用了Collection方法，所以从Collection继承的一个类的任何对象都可以正常工作。但ArrayList是一个典型的Collection，它代替了Vector的位置。</p>

  <p>显然，add()方法的作用是将一个新元素置入集合里。然而，用户文档谨慎地指出add()“保证这个集合包含了指定的元素”。这一点是为Set作铺垫的，后者只有在元素不存在的前提下才会真的加入那个元素。对于ArrayList以及其他任何形式的List，add()肯定意味着“直接加入”。</p>

  <p>利用iterator()方法，所有集合都能生成一个“反复器”（Iterator）。反复器其实就象一个“枚举”（Enumeration），是后者的一个替代物，只是：</p>

  <p>(1) 它采用了一个历史上默认、而且早在OOP中得到广泛采纳的名字（反复器）。</p>

  <p>(2) 采用了比Enumeration更短的名字：hasNext()代替了hasMoreElement()，而next()代替了nextElement()。</p>

  <p>(3) 添加了一个名为remove()的新方法，可删除由Iterator生成的上一个元素。所以每次调用next()的时候，只需调用remove()一次。</p>

  <p>在SimpleCollection.java中，大家可看到创建了一个反复器，并用它在集合里遍历，打印出每个元素。</p>

  <p>8.7.1 使用Collections</p>

  <p>下面这张表格总结了用一个集合能做的所有事情（亦可对Set和List做同样的事情，尽管List还提供了一些额外的功能）。Map不是从Collection继承的，所以要单独对待。</p>

  <p>Boolean add(Object)</p>

  <p>*Ensures that the Collection contains the argument. Returns false if it doesn’t add the argument.</p>

  <p>Boolean addAll(Collection)</p>

  <p>*Adds all the elements in the argument. Returns true if any elements were added.</p>

  <p>void clear( )</p>

  <p>*Removes all the elements in the Collection.</p>

  <p>Boolean contains(Object)</p>

  <p>True if the Collection contains the argument.</p>

  <p>Boolean containsAll(Collection)</p>

  <p>True if the Collection contains all the elements in the argument.</p>

  <p>Boolean isEmpty( )</p>

  <p>True if the Collection has no elements.</p>

  <p>Iterator iterator( )</p>

  <p>Returns an Iterator that you can use to move through the elements in the Collection.</p>

  <p>Boolean remove(Object)</p>

  <p>*If the argument is in the Collection, one instance of that element is removed. Returns true if a removal occurred.</p>

  <p>Boolean removeAll(Collection)</p>

  <p>*Removes all the elements that are contained in the argument. Returns true if any removals occurred.</p>

  <p>Boolean retainAll(Collection)</p>

  <p>*Retains only elements that are contained in the argument (an “intersection” from set theory). Returns true if any changes occurred.</p>

  <p>int size( )</p>

  <p>Returns the number of elements in the Collection.</p>

  <p>Object[] toArray( )</p>

  <p>Returns an array containing all the elements in the Collection.</p>

  <p>Object[] toArray(Object[] a)</p>

  <p>Returns an array containing all the elements in the Collection, whose type is that of the array a rather than plain Object (you must cast the array to the right type). *This is an “optional” method, which means it might not be implemented by a particular Collection. If not, that method throws an UnsupportedOperationException. Exceptions will be covered in Chapter 9.boolean add(Object) ＊保证集合内包含了自变量。如果它没有添加自变量，就返回false（假）</p>

  <p>boolean addAll(Collection) ＊添加自变量内的所有元素。如果没有添加元素，则返回true（真）</p>

  <p>void clear() ＊删除集合内的所有元素</p>

  <p>boolean contains(Object) 若集合包含自变量，就返回“真”</p>

  <p>boolean containsAll(Collection) 若集合包含了自变量内的所有元素，就返回“真”</p>

  <p>boolean isEmpty() 若集合内没有元素，就返回“真”</p>

  <p>Iterator iterator() 返回一个反复器，以用它遍历集合的各元素</p>

  <p>boolean remove(Object) ＊如自变量在集合里，就删除那个元素的一个实例。如果已进行了删除，就返回“真”</p>

  <p>boolean removeAll(Collection) ＊删除自变量里的所有元素。如果已进行了任何删除，就返回“真”</p>

  <p>boolean retainAll(Collection) ＊只保留包含在一个自变量里的元素（一个理论的“交集”）。如果已进行了任何改变，就返回“真”</p>

  <p>int size() 返回集合内的元素数量</p>

  <p>Object[] toArray() 返回包含了集合内所有元素的一个数组</p>

  <p>＊这是一个“可选的”方法，有的集合可能并未实现它。若确实如此，该方法就会遇到一个UnsupportedOperatiionException，即一个“操作不支持”违例，详见第9章。</p>

  <p>下面这个例子向大家演示了所有方法。同样地，它们只对从集合继承的东西有效，一个ArrayList作为一种“不常用的分母”使用：</p>

  <p>//: Collection1.java</p>

  <p>// Things you can do with all Collections</p>

  <p>package c08.newcollections;</p>

  <p>import java.util.*;</p>

  <p>public class Collection1 {</p>

  <p>// Fill with 'size' elements, start</p>

  <p>// counting at 'start':</p>

  <p>public static Collection</p>

  <p>fill(Collection c, int start, int size) {</p>

  <p>for(int i = start; i &lt; start + size; i++)</p>

  <p>c.add(Integer.toString(i));</p>

  <p>return c;</p>

  <p>}</p>

  <p>// Default to a "start" of 0:</p>

  <p>public static Collection</p>

  <p>fill(Collection c, int size) {</p>

  <p>return fill(c, 0, size);</p>

  <p>}</p>

  <p>// Default to 10 elements:</p>

  <p>public static Collection fill(Collection c) {</p>

  <p>return fill(c, 0, 10);</p>

  <p>}</p>

  <p>// Create &amp; upcast to Collection:</p>

  <p>public static Collection newCollection() {</p>

  <p>return fill(new ArrayList());</p>

  <p>// ArrayList is used for simplicity, but it's</p>

  <p>// only seen as a generic Collection</p>

  <p>// everywhere else in the program.</p>

  <p>}</p>

  <p>// Fill a Collection with a range of values:</p>

  <p>public static Collection</p>

  <p>newCollection(int start, int size) {</p>

  <p>return fill(new ArrayList(), start, size);</p>

  <p>}</p>

  <p>// Moving through a List with an iterator:</p>

  <p>public static void print(Collection c) {</p>

  <p>for(Iterator x = c.iterator(); x.hasNext();)</p>

  <p>System.out.print(x.next() + " ");</p>

  <p>System.out.println();</p>

  <p>}</p>

  <p>public static void main(String[] args) {</p>

  <p>Collection c = newCollection();</p>

  <p>c.add("ten");</p>

  <p>c.add("eleven");</p>

  <p>print(c);</p>

  <p>// Make an array from the List:</p>

  <p>Object[] array = c.toArray();</p>

  <p>// Make a String array from the List:</p>

  <p>String[] str =</p>

  <p>(String[])c.toArray(new String[1]);</p>

  <p>// Find max and min elements; this means</p>

  <p>// different things depending on the way</p>

  <p>// the Comparable interface is implemented:</p>

  <p>System.out.println("Collections.max(c) = " +</p>

  <p>Collections.max(c));</p>

  <p>System.out.println("Collections.min(c) = " +</p>

  <p>Collections.min(c));</p>

  <p>// Add a Collection to another Collection</p>

  <p>c.addAll(newCollection());</p>

  <p>print(c);</p>

  <p>c.remove("3"); // Removes the first one</p>

  <p>print(c);</p>

  <p>c.remove("3"); // Removes the second one</p>

  <p>print(c);</p>

  <p>// Remove all components that are in the</p>

  <p>// argument collection:</p>

  <p>c.removeAll(newCollection());</p>

  <p>print(c);</p>

  <p>c.addAll(newCollection());</p>

  <p>print(c);</p>

  <p>// Is an element in this Collection?</p>

  <p>System.out.println(</p>

  <p>"c.contains(\"4\") = " + c.contains("4"));</p>

  <p>// Is a Collection in this Collection?</p>

  <p>System.out.println(</p>

  <p>"c.containsAll(newCollection()) = " +</p>

  <p>c.containsAll(newCollection()));</p>

  <p>Collection c2 = newCollection(5, 3);</p>

  <p>// Keep all the elements that are in both</p>

  <p>// c and c2 (an intersection of sets):</p>

  <p>c.retainAll(c2);</p>

  <p>print(c);</p>

  <p>// Throw away all the elements in c that</p>

  <p>// also appear in c2:</p>

  <p>c.removeAll(c2);</p>

  <p>System.out.println("c.isEmpty() = " +</p>

  <p>c.isEmpty());</p>

  <p>c = newCollection();</p>

  <p>print(c);</p>

  <p>c.clear(); // Remove all elements</p>

  <p>System.out.println("after c.clear():");</p>

  <p>print(c);</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>通过第一个方法，我们可用测试数据填充任何集合。在当前这种情况下，只是将int转换成String。第二个方法将在本章其余的部分经常采用。</p>

  <p>newCollection()的两个版本都创建了ArrayList，用于包含不同的数据集，并将它们作为集合对象返回。所以很明显，除了Collection接口之外，不会再用到其他什么。</p>

  <p>print()方法也会在本节经常用到。由于它用一个反复器（Iterator）在一个集合内遍历，而任何集合都可以产生这样的一个反复器，所以它适用于List和Set，也适用于由一个Map生成的Collection。</p>

  <p>main()用简单的手段显示出了集合内的所有方法。</p>

  <p>在后续的小节里，我们将比较List，Set和Map的不同实现方案，同时指出在各种情况下哪一种方案应成为首选（带有星号的那个）。大家会发现这里并未包括一些传统的类，如Vector，Stack以及Hashtable等。因为不管在什么情况下，新集合内都有自己首选的类。</p>

  <p>8.7.2 使用Lists</p>

  <p>List (interface)</p>

  <p>Order is the most important feature of a List; it promises to maintain elements in a particular sequence. List adds a number of methods to Collection that allow insertion and removal of elements in the middle of a List. (This is recommended only for a LinkedList.) A List will produce a ListIterator, and using this you can traverse the List in both directions, as well as insert and remove elements in the middle of the list (again, recommended only for a LinkedList).</p>

  <p>ArrayList*</p>

  <p>A List backed by an array. Use instead of Vector as a general-purpose object holder. Allows rapid random access to elements, but is slow when inserting and removing elements from the middle of a list. ListIterator should be used only for back-and-forth traversal of an ArrayList, but not for inserting and removing elements, which is expensive compared to LinkedList.</p>

  <p>LinkedList</p>

  <p>Provides optimal sequential access, with inexpensive insertions and deletions from the middle of the list. Relatively slow for random access. (Use ArrayList instead.) Also has addFirst( ), addLast( ), getFirst( ), getLast( ), removeFirst( ), and removeLast( ) (which are not defined in any interfaces or base classes) to allow it to be used as a stack, a queue, and a dequeue.List（接口） 顺序是List最重要的特性；它可保证元素按照规定的顺序排列。List为Collection添加了大量方法，以便我们在List中部插入和删除元素（只推荐对LinkedList这样做）。List也会生成一个ListIterator（列表反复器），利用它可在一个列表里朝两个方向遍历，同时插入和删除位于列表中部的元素（同样地，只建议对LinkedList这样做）</p>

  <p>ArrayList＊ 由一个数组后推得到的List。作为一个常规用途的对象容器使用，用于替换原先的Vector。允许我们快速访问元素，但在从列表中部插入和删除元素时，速度却嫌稍慢。一般只应该用ListIterator对一个ArrayList进行向前和向后遍历，不要用它删除和插入元素；与LinkedList相比，它的效率要低许多</p>

  <p>LinkedList 提供优化的顺序访问性能，同时可以高效率地在列表中部进行插入和删除操作。但在进行随机访问时，速度却相当慢，此时应换用ArrayList。也提供了addFirst()，addLast()，getFirst()，getLast()，removeFirst()以及removeLast()（未在任何接口或基础类中定义），以便将其作为一个规格、队列以及一个双向队列使用</p>

  <p>下面这个例子中的方法每个都覆盖了一组不同的行为：每个列表都能做的事情（basicTest()），通过一个反复器遍历（iterMotion()）、用一个反复器改变某些东西（iterManipulation()）、体验列表处理的效果（testVisual()）以及只有LinkedList才能做的事情等：</p>

  <p>//: List1.java</p>

  <p>// Things you can do with Lists</p>

  <p>package c08.newcollections;</p>

  <p>import java.util.*;</p>

  <p>public class List1 {</p>

  <p>// Wrap Collection1.fill() for convenience:</p>

  <p>public static List fill(List a) {</p>

  <p>return (List)Collection1.fill(a);</p>

  <p>}</p>

  <p>// You can use an Iterator, just as with a</p>

  <p>// Collection, but you can also use random</p>

  <p>// access with get():</p>

  <p>public static void print(List a) {</p>

  <p>for(int i = 0; i &lt; a.size(); i++)</p>

  <p>System.out.print(a.get(i) + " ");</p>

  <p>System.out.println();</p>

  <p>}</p>

  <p>static boolean b;</p>

  <p>static Object o;</p>

  <p>static int i;</p>

  <p>static Iterator it;</p>

  <p>static ListIterator lit;</p>

  <p>public static void basicTest(List a) {</p>

  <p>a.add(1, "x"); // Add at location 1</p>

  <p>a.add("x"); // Add at end</p>

  <p>// Add a collection:</p>

  <p>a.addAll(fill(new ArrayList()));</p>

  <p>// Add a collection starting at location 3:</p>

  <p>a.addAll(3, fill(new ArrayList()));</p>

  <p>b = a.contains("1"); // Is it in there?</p>

  <p>// Is the entire collection in there?</p>

  <p>b = a.containsAll(fill(new ArrayList()));</p>

  <p>// Lists allow random access, which is cheap</p>

  <p>// for ArrayList, expensive for LinkedList:</p>

  <p>o = a.get(1); // Get object at location 1</p>

  <p>i = a.indexOf("1"); // Tell index of object</p>

  <p>// indexOf, starting search at location 2:</p>

  <p>i = a.indexOf("1", 2);</p>

  <p>b = a.isEmpty(); // Any elements inside?</p>

  <p>it = a.iterator(); // Ordinary Iterator</p>

  <p>lit = a.listIterator(); // ListIterator</p>

  <p>lit = a.listIterator(3); // Start at loc 3</p>

  <p>i = a.lastIndexOf("1"); // Last match</p>

  <p>i = a.lastIndexOf("1", 2); // ...after loc 2</p>

  <p>a.remove(1); // Remove location 1</p>

  <p>a.remove("3"); // Remove this object</p>

  <p>a.set(1, "y"); // Set location 1 to "y"</p>

  <p>// Keep everything that's in the argument</p>

  <p>// (the intersection of the two sets):</p>

  <p>a.retainAll(fill(new ArrayList()));</p>

  <p>// Remove elements in this range:</p>

  <p>a.removeRange(0, 2);</p>

  <p>// Remove everything that's in the argument:</p>

  <p>a.removeAll(fill(new ArrayList()));</p>

  <p>i = a.size(); // How big is it?</p>

  <p>a.clear(); // Remove all elements</p>

  <p>}</p>

  <p>public static void iterMotion(List a) {</p>

  <p>ListIterator it = a.listIterator();</p>

  <p>b = it.hasNext();</p>

  <p>b = it.hasPrevious();</p>

  <p>o = it.next();</p>

  <p>i = it.nextIndex();</p>

  <p>o = it.previous();</p>

  <p>i = it.previousIndex();</p>

  <p>}</p>

  <p>public static void iterManipulation(List a) {</p>

  <p>ListIterator it = a.listIterator();</p>

  <p>it.add("47");</p>

  <p>// Must move to an element after add():</p>

  <p>it.next();</p>

  <p>// Remove the element that was just produced:</p>

  <p>it.remove();</p>

  <p>// Must move to an element after remove():</p>

  <p>it.next();</p>

  <p>// Change the element that was just produced:</p>

  <p>it.set("47");</p>

  <p>}</p>

  <p>public static void testVisual(List a) {</p>

  <p>print(a);</p>

  <p>List b = new ArrayList();</p>

  <p>fill(b);</p>

  <p>System.out.print("b = ");</p>

  <p>print(b);</p>

  <p>a.addAll(b);</p>

  <p>a.addAll(fill(new ArrayList()));</p>

  <p>print(a);</p>

  <p>// Shrink the list by removing all the</p>

  <p>// elements beyond the first 1/2 of the list</p>

  <p>System.out.println(a.size());</p>

  <p>System.out.println(a.size()/2);</p>

  <p>a.removeRange(a.size()/2, a.size()/2 + 2);</p>

  <p>print(a);</p>

  <p>// Insert, remove, and replace elements</p>

  <p>// using a ListIterator:</p>

  <p>ListIterator x = a.listIterator(a.size()/2);</p>

  <p>x.add("one");</p>

  <p>print(a);</p>

  <p>System.out.println(x.next());</p>

  <p>x.remove();</p>

  <p>System.out.println(x.next());</p>

  <p>x.set("47");</p>

  <p>print(a);</p>

  <p>// Traverse the list backwards:</p>

  <p>x = a.listIterator(a.size());</p>

  <p>while(x.hasPrevious())</p>

  <p>System.out.print(x.previous() + " ");</p>

  <p>System.out.println();</p>

  <p>System.out.println("testVisual finished");</p>

  <p>}</p>

  <p>// There are some things that only</p>

  <p>// LinkedLists can do:</p>

  <p>public static void testLinkedList() {</p>

  <p>LinkedList ll = new LinkedList();</p>

  <p>Collection1.fill(ll, 5);</p>

  <p>print(ll);</p>

  <p>// Treat it like a stack, pushing:</p>

  <p>ll.addFirst("one");</p>

  <p>ll.addFirst("two");</p>

  <p>print(ll);</p>

  <p>// Like "peeking" at the top of a stack:</p>

  <p>System.out.println(ll.getFirst());</p>

  <p>// Like popping a stack:</p>

  <p>System.out.println(ll.removeFirst());</p>

  <p>System.out.println(ll.removeFirst());</p>

  <p>// Treat it like a queue, pulling elements</p>

  <p>// off the tail end:</p>

  <p>System.out.println(ll.removeLast());</p>

  <p>// With the above operations, it's a dequeue!</p>

  <p>print(ll);</p>

  <p>}</p>

  <p>public static void main(String args[]) {</p>

  <p>// Make and fill a new list each time:</p>

  <p>basicTest(fill(new LinkedList()));</p>

  <p>basicTest(fill(new ArrayList()));</p>

  <p>iterMotion(fill(new LinkedList()));</p>

  <p>iterMotion(fill(new ArrayList()));</p>

  <p>iterManipulation(fill(new LinkedList()));</p>

  <p>iterManipulation(fill(new ArrayList()));</p>

  <p>testVisual(fill(new LinkedList()));</p>

  <p>testLinkedList();</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>在basicTest()和iterMotiion()中，只是简单地发出调用，以便揭示出正确的语法。而且尽管捕获了返回值，但是并未使用它。在某些情况下，之所以不捕获返回值，是由于它们没有什么特别的用处。在正式使用它们前，应仔细研究一下自己的联机文档，掌握这些方法完整、正确的用法。</p>

  <p>8.7.3 使用Sets</p>

  <p>Set拥有与Collection完全相同的接口，所以和两种不同的List不同，它没有什么额外的功能。相反，Set完全就是一个Collection，只是具有不同的行为（这是实例和多形性最理想的应用：用于表达不同的行为）。在这里，一个Set只允许每个对象存在一个实例（正如大家以后会看到的那样，一个对象的“值”的构成是相当复杂的）。</p>

  <p>Set (interface)</p>

  <p>Each element that you add to the Set must be unique; otherwise the Set doesn’t add the duplicate element. Objects added to a Set must define equals( ) to establish object uniqueness. Set has exactly the same interface as Collection. The Set interface does not guarantee it will maintain its elements in any particular order.</p>

  <p>HashSet*</p>

  <p>For Sets where fast lookup time is important. Objects must also define hashCode( ).</p>

  <p>TreeSet</p>

  <p>An ordered Set backed by a red-black tree. This way, you can extract an ordered sequence from a Set.Set（接口） 添加到Set的每个元素都必须是独一无二的；否则Set就不会添加重复的元素。添加到Set里的对象必须定义equals()，从而建立对象的唯一性。Set拥有与Collection完全相同的接口。一个Set不能保证自己可按任何特定的顺序维持自己的元素</p>

  <p>HashSet＊ 用于除非常小的以外的所有Set。对象也必须定义hashCode()</p>

  <p>ArraySet 由一个数组后推得到的Set。面向非常小的Set设计，特别是那些需要频繁创建和删除的。对于小Set，与HashSet相比，ArraySet创建和反复所需付出的代价都要小得多。但随着Set的增大，它的性能也会大打折扣。不需要HashCode()</p>

  <p>TreeSet 由一个“红黑树”后推得到的顺序Set（注释⑦）。这样一来，我们就可以从一个Set里提到一个顺序集合</p>

  <p>⑦：直至本书写作的时候，TreeSet仍然只是宣布，尚未正式实现。所以这里没有提供使用TreeSet的例子。</p>

  <p>下面这个例子并没有列出用一个Set能够做的全部事情，因为接口与Collection是相同的，前例已经练习过了。相反，我们要例示的重点在于使一个Set独一无二的行为：</p>

  <p>//: Set1.java</p>

  <p>// Things you can do with Sets</p>

  <p>package c08.newcollections;</p>

  <p>import java.util.*;</p>

  <p>public class Set1 {</p>

  <p>public static void testVisual(Set a) {</p>

  <p>Collection1.fill(a);</p>

  <p>Collection1.fill(a);</p>

  <p>Collection1.fill(a);</p>

  <p>Collection1.print(a); // No duplicates!</p>

  <p>// Add another set to this one:</p>

  <p>a.addAll(a);</p>

  <p>a.add("one");</p>

  <p>a.add("one");</p>

  <p>a.add("one");</p>

  <p>Collection1.print(a);</p>

  <p>// Look something up:</p>

  <p>System.out.println("a.contains(\"one\"): " +</p>

  <p>a.contains("one"));</p>

  <p>}</p>

  <p>public static void main(String[] args) {</p>

  <p>testVisual(new HashSet());</p>

  <p>testVisual(new TreeSet());</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>重复的值被添加到Set，但在打印的时候，我们会发现Set只接受每个值的一个实例。</p>

  <p>运行这个程序时，会注意到由HashSet维持的顺序与ArraySet是不同的。这是由于它们采用了不同的方法来保存元素，以便它们以后的定位。ArraySet保持着它们的顺序状态，而HashSet使用一个散列函数，这是特别为快速检索设计的）。创建自己的类型时，一定要注意Set需要通过一种方式来维持一种存储顺序，就象本章早些时候展示的“groundhog”（土拔鼠）例子那样。下面是一个例子：</p>

  <p>//: Set2.java</p>

  <p>// Putting your own type in a Set</p>

  <p>package c08.newcollections;</p>

  <p>import java.util.*;</p>

  <p>class MyType implements Comparable {</p>

  <p>private int i;</p>

  <p>public MyType(int n) { i = n; }</p>

  <p>public boolean equals(Object o) {</p>

  <p>return</p>

  <p>(o instanceof MyType)</p>

  <p>&amp;&amp; (i == ((MyType)o).i);</p>

  <p>}</p>

  <p>public int hashCode() { return i; }</p>

  <p>public String toString() { return i + " "; }</p>

  <p>public int compareTo(Object o) {</p>

  <p>int i2 = ((MyType) o).i;</p>

  <p>return (i2 &lt; i ? -1 : (i2 == i ? 0 : 1));</p>

  <p>}</p>

  <p>}</p>

  <p>public class Set2 {</p>

  <p>public static Set fill(Set a, int size) {</p>

  <p>for(int i = 0; i &lt; size; i++)</p>

  <p>a.add(new MyType(i));</p>

  <p>return a;</p>

  <p>}</p>

  <p>public static Set fill(Set a) {</p>

  <p>return fill(a, 10);</p>

  <p>}</p>

  <p>public static void test(Set a) {</p>

  <p>fill(a);</p>

  <p>fill(a); // Try to add duplicates</p>

  <p>fill(a);</p>

  <p>a.addAll(fill(new TreeSet()));</p>

  <p>System.out.println(a);</p>

  <p>}</p>

  <p>public static void main(String[] args) {</p>

  <p>test(new HashSet());</p>

  <p>test(new TreeSet());</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>对equals()及hashCode()的定义遵照“groundhog”例子已经给出的形式。在两种情况下都必须定义一个equals()。但只有要把类置入一个HashSet的前提下，才有必要使用hashCode()――这种情况是完全有可能的，因为通常应先选择作为一个Set实现。</p>

  <p>8.7.4 使用Maps</p>

  <p>Map (interface)</p>

  <p>Maintains key-value associations (pairs), so you can look up a value using a key.</p>

  <p>HashMap*</p>

  <p>Implementation based on a hash table. (Use this instead of Hashtable.) Provides constant-time performance for inserting and locating pairs. Performance can be adjusted via constructors that allow you to set the capacity and load factor of the hash table.</p>

  <p>TreeMap</p>

  <p>Implementation based on a red-black tree. When you view the keys or the pairs, they will be in sorted order (determined by Comparable or Comparator, discussed later). The point of a TreeMap is that you get the results in sorted order. TreeMap is the only Map with the subMap( ) method, which allows you to return a portion of the tree.Map（接口） 维持“键－值”对应关系（对），以便通过一个键查找相应的值</p>

  <p>HashMap＊ 基于一个散列表实现（用它代替Hashtable）。针对“键－值”对的插入和检索，这种形式具有最稳定的性能。可通过构建器对这一性能进行调整，以便设置散列表的“能力”和“装载因子”</p>

  <p>ArrayMap 由一个ArrayList后推得到的Map。对反复的顺序提供了精确的控制。面向非常小的Map设计，特别是那些需要经常创建和删除的。对于非常小的Map，创建和反复所付出的代价要比HashMap低得多。但在Map变大以后，性能也会相应地大幅度降低</p>

  <p>TreeMap 在一个“红－黑”树的基础上实现。查看键或者“键－值”对时，它们会按固定的顺序排列（取决于Comparable或Comparator，稍后即会讲到）。TreeMap最大的好处就是我们得到的是已排好序的结果。TreeMap是含有subMap()方法的唯一一种Map，利用它可以返回树的一部分</p>

  <p>下例包含了两套测试数据以及一个fill()方法，利用该方法可以用任何两维数组（由Object构成）填充任何Map。这些工具也会在其他Map例子中用到。</p>

  <p>//: Map1.java</p>

  <p>// Things you can do with Maps</p>

  <p>package c08.newcollections;</p>

  <p>import java.util.*;</p>

  <p>public class Map1 {</p>

  <p>public final static String[][] testData1 = {</p>

  <p>{ "Happy", "Cheerful disposition" },</p>

  <p>{ &amp;quo</p>

  <div class="mbppagebreak"></div>
</body>
</html>
